package com.zlc.workflow.core.process.cmd;

import com.zlc.common.util.AssertUtils;
import com.zlc.common.util.wrapper.CollectionHelper;
import com.zlc.workflow.constant.NodeType;
import com.zlc.workflow.core.instance.cmd.AbstractCmd;
import com.zlc.workflow.core.process.model.FlowNode;
import com.zlc.workflow.core.process.model.FlowPath;
import com.zlc.workflow.core.process.model.FlowProcess;
import com.zlc.workflow.flow.NodeModel;
import com.zlc.workflow.flow.NodeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
public class ParseJsonToXmlCmd extends AbstractCmd<String> {

    /**
     * json流程内容
     */
    private final FlowProcess flowProcess;

    //流程xml内容
    private final StringBuilder processXml;

    //开始节点
    private final FlowNode startNode;

    //已构建的节点ID，避免重复构建
    private final List<String> parseNodeIds;

    /**
     * 获取当前命令对象的所有带 @Autowired注解的属性，然后从spring上下文中获取到注入进去
     *
     */
    public ParseJsonToXmlCmd(Object params) {
        super(params);
        this.flowProcess = (FlowProcess) params;
        this.processXml = new StringBuilder();
        this.processXml.append("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
                "<!DOCTYPE process PUBLIC \"-//workflow\" \"workflow-1-process.dtd\">\n");
        List<FlowNode> nodes = flowProcess.getNodes();
        //开始节点
        List<FlowNode> startNodes = nodes.stream().filter(n -> NodeType.START.value().equals(n.getNodeType())).collect(Collectors.toList());
        AssertUtils.isTrue(!ObjectUtils.isEmpty(startNodes) && startNodes.size() == 1,"开始节点缺失或存在多个");
        this.startNode = startNodes.get(0);
        //结束节点
        List<FlowNode> endNodes = nodes.stream().filter(n -> NodeType.END.value().equals(n.getNodeType())).collect(Collectors.toList());
        AssertUtils.isTrue(!ObjectUtils.isEmpty(endNodes) && endNodes.size() == 1,"结束节点缺失或存在多个");
        parseNodeIds = new ArrayList<>();
    }

    @Override
    public String execute() {
        String processCode = flowProcess.getProcessCode();
        String processName = flowProcess.getProcessName();
        this.processXml.append("<process processCode=\"").append(processCode).append("\" ")
                .append("processName=\"").append(processName).append("\">\n");
        buildXmlNode(this.startNode);
        this.processXml.append("</process>");
        return this.processXml.toString();
    }


    private void buildXmlNode(FlowNode node){
        //校验节点参数
        checkNodeParam(node);
        List<FlowPath> paths = node.getPaths();

        String nodeType = node.getNodeType();
        if(!(NodeType.JOIN.value().equals(nodeType) || NodeType.END.value().equals(nodeType))){
            buildNodeXml(node);
            for(FlowPath path : paths){
                FlowNode nextNode = getNextNode(path);
                buildXmlNode(nextNode);
            }
        }
        if((NodeType.JOIN.value().equals(nodeType) || NodeType.END.value().equals(nodeType))
                && joinNextCheck(node)){
            buildNodeXml(node);
            for(FlowPath path : paths){
                FlowNode nextNode = getNextNode(path);
                buildXmlNode(nextNode);
            }
        }
    }

    private void buildNodeXml(FlowNode node){
        if(!parseNodeIds.contains(node.getNodeId())) {
            List<FlowPath> paths = node.getPaths();
            //构建节点
            this.processXml.append("    <").append(node.getNodeType()).append(" nodeId=\"").append(node.getNodeId()).append("\" ");
            if (!ObjectUtils.isEmpty(node.getNodeNum())) {
                this.processXml.append("nodeNum=\"").append(node.getNodeNum()).append("\" ");
            }
            this.processXml.append("nodeName=\"").append(node.getNodeName()).append("\" ")
                    .append("x=\"").append(node.getX()).append("\" ")
                    .append("y=\"").append(node.getY());
            if(!NodeType.END.value().equals(node.getNodeType())) {
                this.processXml.append("\">\n");
            }else{
                this.processXml.append("\"/>\n");
            }
            //构建路线
            for (FlowPath path : paths) {
                buildPathXml(path);
            }
            if(!NodeType.END.value().equals(node.getNodeType())) {
                this.processXml.append("    </").append(node.getNodeType()).append(">\n");
            }
            this.parseNodeIds.add(node.getNodeId());
        }
    }

    /**
     * 校验分支节点是否可以继续向后走
     */
    private boolean joinNextCheck(FlowNode joinNode){
        String nodeId = joinNode.getNodeId();
        List<String> preNodeIds = new ArrayList<>();
        List<FlowNode> nodes = flowProcess.getNodes();
        for(FlowNode node : nodes){
            List<FlowPath> paths = node.getPaths();
            for(FlowPath path : paths){
                if(nodeId.equals(path.getTo())){
                    preNodeIds.add(node.getNodeId());
                }
            }
        }
        return parseNodeIds.containsAll(preNodeIds);
    }

    /**
     * 构建路线xml
     */
    private void buildPathXml(FlowPath path){
        this.processXml.append("        <").append(path.getNodeType()).append(" nodeId=\"").append(path.getNodeId()).append("\" ");
        if(!ObjectUtils.isEmpty(path.getNodeNum())){
            this.processXml.append("nodeNum=\"").append(path.getNodeNum()).append("\" ");
        }
        this.processXml.append("nodeName=\"").append(path.getNodeName()).append("\" ")
                .append("source=\"").append(path.getSource()).append("\" ")
                .append("target=\"").append(path.getTarget()).append("\" ")
                .append("to=\"").append(path.getTo()).append("\"/>\n");
    }

    private void checkNodeParam(FlowNode node){
        String nodeId = node.getNodeId();
        String nodeName = node.getNodeName();
        String nodeType = node.getNodeType();
        List<FlowPath> paths = node.getPaths();
        //校验节点类型
        NodeType type = NodeType.getNodeType(nodeType);
        AssertUtils.isTrue(!ObjectUtils.isEmpty(type),"不存在的节点类型->"+nodeType);
        AssertUtils.isTrue(!ObjectUtils.isEmpty(nodeId),"节点ID缺失");
        AssertUtils.isTrue(!ObjectUtils.isEmpty(nodeName),"节点名称缺失");
        if(!NodeType.END.value().equals(nodeType)){
            AssertUtils.isTrue(!ObjectUtils.isEmpty(paths),"节点下一路线缺失");
        }
    }


    private FlowNode getNextNode(FlowPath path){
        String to = path.getTo();
        AssertUtils.isTrue(!ObjectUtils.isEmpty(to),"路线缺少下一节点ID");
        List<FlowNode> nodes = this.flowProcess.getNodes();
        FlowNode node = nodes.stream().filter(n -> to.equals(n.getNodeId())).findFirst().orElse(null);
        AssertUtils.isTrue(!ObjectUtils.isEmpty(node),"路线"+path.getNodeId()+"配置错误，下一节点无效");
        return node;
    }
}
